/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.bpm.core.json.converter.json.converter;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.je.bpm.core.model.*;
import com.je.bpm.core.model.config.sequenceflow.SequenceFlowStyleConfig;
import com.je.bpm.core.model.gateway.ExclusiveGateway;
import org.apache.commons.lang3.StringUtils;

import java.util.Map;

public class SequenceFlowJsonConverter extends BaseBpmnJsonConverter {

    public static void fillTypes(Map<String, Class<? extends BaseBpmnJsonConverter>> convertersToBpmnMap, Map<Class<? extends BaseElement>, Class<? extends BaseBpmnJsonConverter>> convertersToJsonMap) {
        fillJsonTypes(convertersToBpmnMap);
        fillBpmnTypes(convertersToJsonMap);
    }

    public static void fillJsonTypes(Map<String, Class<? extends BaseBpmnJsonConverter>> convertersToBpmnMap) {
        convertersToBpmnMap.put(STENCIL_SEQUENCE_FLOW, SequenceFlowJsonConverter.class);
    }

    public static void fillBpmnTypes(Map<Class<? extends BaseElement>, Class<? extends BaseBpmnJsonConverter>> convertersToJsonMap) {
        convertersToJsonMap.put(SequenceFlow.class, SequenceFlowJsonConverter.class);
    }

    @Override
    protected String getStencilId(BaseElement baseElement) {
        return STENCIL_SEQUENCE_FLOW;
    }

    @Override
    public void convertToJson(BaseElement baseElement, ActivityProcessor processor, BpmnModel model, FlowElementsContainer container, ArrayNode shapesArrayNode, double subProcessX, double subProcessY) {
        SequenceFlow sequenceFlow = (SequenceFlow) baseElement;
        ObjectNode flowNode = BpmnJsonConverterUtil.createChildShape(sequenceFlow.getId(), STENCIL_SEQUENCE_FLOW, 172, 212, 128, 212);
        ArrayNode dockersArrayNode = objectMapper.createArrayNode();
        ObjectNode dockNode = objectMapper.createObjectNode();
        dockNode.put(EDITOR_BOUNDS_X, model.getGraphicInfo(sequenceFlow.getSourceRef()) == null ? 0 : model.getGraphicInfo(sequenceFlow.getSourceRef()).getWidth() / 2.0);
        dockNode.put(EDITOR_BOUNDS_Y, model.getGraphicInfo(sequenceFlow.getSourceRef()) == null ? 0 : model.getGraphicInfo(sequenceFlow.getSourceRef()).getHeight() / 2.0);
        dockersArrayNode.add(dockNode);

        if (model.getFlowLocationGraphicInfo(sequenceFlow.getId()).size() > 2) {
            for (int i = 1; i < model.getFlowLocationGraphicInfo(sequenceFlow.getId()).size() - 1; i++) {
                GraphicInfo graphicInfo = model.getFlowLocationGraphicInfo(sequenceFlow.getId()).get(i);
                dockNode = objectMapper.createObjectNode();
                dockNode.put(EDITOR_BOUNDS_X, graphicInfo.getX());
                dockNode.put(EDITOR_BOUNDS_Y, graphicInfo.getY());
                dockersArrayNode.add(dockNode);
            }
        }

        dockNode = objectMapper.createObjectNode();
        dockNode.put(EDITOR_BOUNDS_X, model.getGraphicInfo(sequenceFlow.getTargetRef()) == null ? 0 : model.getGraphicInfo(sequenceFlow.getTargetRef()).getWidth() / 2.0);
        dockNode.put(EDITOR_BOUNDS_Y, model.getGraphicInfo(sequenceFlow.getTargetRef()) == null ? 0 : model.getGraphicInfo(sequenceFlow.getTargetRef()).getHeight() / 2.0);
        dockersArrayNode.add(dockNode);
        flowNode.set("dockers", dockersArrayNode);
        ArrayNode outgoingArrayNode = objectMapper.createArrayNode();
        outgoingArrayNode.add(BpmnJsonConverterUtil.createResourceNode(sequenceFlow.getTargetRef()));
        flowNode.set("outgoing", outgoingArrayNode);
        flowNode.set("target", BpmnJsonConverterUtil.createResourceNode(sequenceFlow.getTargetRef()));

        ObjectNode propertiesNode = objectMapper.createObjectNode();
        propertiesNode.put(PROPERTY_OVERRIDE_ID, sequenceFlow.getId());
        if (StringUtils.isNotEmpty(sequenceFlow.getName())) {
            propertiesNode.put(PROPERTY_NAME, sequenceFlow.getName());
        }

        if (StringUtils.isNotEmpty(sequenceFlow.getDocumentation())) {
            propertiesNode.put(PROPERTY_DOCUMENTATION, sequenceFlow.getDocumentation());
        }

        if (StringUtils.isNotEmpty(sequenceFlow.getConditionExpression())) {
            propertiesNode.put(PROPERTY_SEQUENCEFLOW_CONDITION, sequenceFlow.getConditionExpression());
        }

        if (StringUtils.isNotEmpty(sequenceFlow.getSourceRef())) {

            FlowElement sourceFlowElement = container.getFlowElement(sequenceFlow.getSourceRef());
            if (sourceFlowElement != null) {
                String defaultFlowId = null;
                if (sourceFlowElement instanceof ExclusiveGateway) {
                    ExclusiveGateway parentExclusiveGateway = (ExclusiveGateway) sourceFlowElement;
                    defaultFlowId = parentExclusiveGateway.getDefaultFlow();
                } else if (sourceFlowElement instanceof Activity) {
                    Activity parentActivity = (Activity) sourceFlowElement;
                    defaultFlowId = parentActivity.getDefaultFlow();
                }

                if (defaultFlowId != null && defaultFlowId.equals(sequenceFlow.getId())) {
                    propertiesNode.put(PROPERTY_SEQUENCEFLOW_DEFAULT, true);
                }

            }
        }

        if (sequenceFlow.getExecutionListeners().size() > 0) {
            BpmnJsonConverterUtil.convertListenersToJson(sequenceFlow.getExecutionListeners(), true, propertiesNode);
        }

        flowNode.set(EDITOR_SHAPE_PROPERTIES, propertiesNode);
        convertElementStyleConfigToJson(sequenceFlow, flowNode);
        shapesArrayNode.add(flowNode);
    }

    private void convertElementStyleConfigToJson(SequenceFlow sequenceFlow, ObjectNode flowNode) {
        SequenceFlowStyleConfig sequenceFlowStyleConfig = sequenceFlow.getStyleConfig();
        ObjectNode styleNode = objectMapper.createObjectNode();
        styleNode.put(SEQUENCE_FLOW_STYLE_DOTTEDLINE, convertBooleanToIntJson(sequenceFlowStyleConfig.getDottedLine()));
        styleNode.put(SEQUENCE_FLOW_STYLE_COLOR, sequenceFlowStyleConfig.getColor());
        styleNode.put(SEQUENCE_FLOW_STYLE_FONTSIZE, sequenceFlowStyleConfig.getFontSize());
        styleNode.put(SEQUENCE_FLOW_STYLE_FONTSTYLE, sequenceFlowStyleConfig.getFontStyle());
        styleNode.put(SEQUENCE_FLOW_STYLE_FONTCOLOR, sequenceFlowStyleConfig.getFontColor());
        flowNode.set(SEQUENCE_FLOW_STYLE_CONFIG, styleNode);
    }

    private void convertJsonToElementStyleConfig(JsonNode elementNode, SequenceFlow flow) {
        JsonNode styleNode = elementNode.get(SEQUENCE_FLOW_STYLE_CONFIG);
        SequenceFlowStyleConfig sequenceFlowStyleConfig = new SequenceFlowStyleConfig();
        sequenceFlowStyleConfig.setColor(getValueAsString(SEQUENCE_FLOW_STYLE_COLOR, styleNode));
        sequenceFlowStyleConfig.setDottedLine(getValueAsBoolean(SEQUENCE_FLOW_STYLE_DOTTEDLINE, styleNode));
        sequenceFlowStyleConfig.setFontColor(getValueAsString(SEQUENCE_FLOW_STYLE_FONTCOLOR, styleNode));
        sequenceFlowStyleConfig.setFontSize(getValueAsString(SEQUENCE_FLOW_STYLE_FONTSIZE, styleNode));
        sequenceFlowStyleConfig.setFontStyle(getValueAsString(SEQUENCE_FLOW_STYLE_FONTSTYLE, styleNode));
        flow.setStyleConfig(sequenceFlowStyleConfig);
    }

    @Override
    protected void convertElementToJson(ObjectNode propertiesNode, BaseElement baseElement) {
        // nothing to do
    }

    @Override
    protected FlowElement convertJsonToElement(JsonNode elementNode, JsonNode modelNode, Map<String, JsonNode> shapeMap) {
        SequenceFlow flow = new SequenceFlow();

        String sourceRef = BpmnJsonConverterUtil.lookForSourceRef(elementNode.get(EDITOR_SHAPE_ID).asText(), modelNode.get(EDITOR_CHILD_SHAPES));
        if (sourceRef != null) {
            flow.setSourceRef(sourceRef);
            JsonNode targetNode = elementNode.get(EDITOR_TARGET);
            if (targetNode != null && !targetNode.isNull() && targetNode.size() > 0) {
                String targetId = targetNode.get(0).get(EDITOR_SHAPE_ID).asText();
                if (shapeMap.get(targetId) != null) {
                    flow.setTargetRef(BpmnJsonConverterUtil.getElementId(shapeMap.get(targetId)));
                }
            }
        }

        if (elementNode.get("properties") != null) {
            if (elementNode.get("properties").get("conditionExpression") != null) {
                flow.setConditionExpression(elementNode.get("properties").get("conditionExpression").asText());
            }
        }

        JsonNode conditionNode = getProperty(PROPERTY_SEQUENCEFLOW_CONDITION, elementNode);
        if (conditionNode != null) {

            if (conditionNode.isTextual() && !conditionNode.isNull()) {
                flow.setConditionExpression(conditionNode.asText());

            } else if (conditionNode.get("expression") != null) {
                JsonNode expressionNode = conditionNode.get("expression");
                if (expressionNode.get("type") != null) {
                    String expressionType = expressionNode.get("type").asText();

                    if ("variables".equalsIgnoreCase(expressionType) && expressionNode.get("fieldType") != null) {

                        String fieldType = expressionNode.get("fieldType").asText();

                        if ("field".equalsIgnoreCase(fieldType)) {
                            setFieldConditionExpression(flow, expressionNode);

                        } else if ("outcome".equalsIgnoreCase(fieldType)) {
                            setOutcomeConditionExpression(flow, expressionNode);
                        }

                    } else if (expressionNode.get("staticValue") != null && !(expressionNode.get("staticValue").isNull())) {
                        flow.setConditionExpression(expressionNode.get("staticValue").asText());
                    }
                }
            }
        }
        convertJsonToElementStyleConfig(elementNode, flow);
        return flow;
    }

    protected void setFieldConditionExpression(SequenceFlow flow, JsonNode expressionNode) {
        String fieldId = null;
        if (expressionNode.get("fieldId") != null && !(expressionNode.get("fieldId").isNull())) {
            fieldId = expressionNode.get("fieldId").asText();
        }

        String operator = null;
        if (expressionNode.get("operator") != null && !(expressionNode.get("operator").isNull())) {
            operator = expressionNode.get("operator").asText();
        }

        String value = null;
        if (expressionNode.get("value") != null && !(expressionNode.get("value").isNull())) {
            value = expressionNode.get("value").asText();
        }

        if (fieldId != null && operator != null && value != null) {
            flow.setConditionExpression("${" + fieldId + " " + operator + " " + value + "}");
            addExtensionElement("conditionFieldId", fieldId, flow);
            addExtensionElement("conditionOperator", operator, flow);
            addExtensionElement("conditionValue", value, flow);
        }
    }

    protected void setOutcomeConditionExpression(SequenceFlow flow, JsonNode expressionNode) {
        Long formId = null;
        if (expressionNode.get("outcomeFormId") != null && !(expressionNode.get("outcomeFormId").isNull())) {
            formId = expressionNode.get("outcomeFormId").asLong();
        }

        String operator = null;
        if (expressionNode.get("operator") != null && expressionNode.get("operator").isNull() == false) {
            operator = expressionNode.get("operator").asText();
        }

        String outcomeName = null;
        if (expressionNode.get("outcomeName") != null && !(expressionNode.get("outcomeName").isNull())) {
            outcomeName = expressionNode.get("outcomeName").asText();
        }

        if (formId != null && operator != null && outcomeName != null) {
            flow.setConditionExpression("${form" + formId + "outcome " + operator + " " + outcomeName + "}");
            addExtensionElement("conditionFormId", String.valueOf(formId), flow);
            addExtensionElement("conditionOperator", operator, flow);
            addExtensionElement("conditionOutcomeName", outcomeName, flow);
        }
    }

    protected void addExtensionElement(String name, String value, SequenceFlow flow) {
        ExtensionElement extensionElement = new ExtensionElement();
        extensionElement.setNamespace(NAMESPACE);
        extensionElement.setNamespacePrefix("modeler");
        extensionElement.setName(name);
        extensionElement.setElementText(value);
        flow.addExtensionElement(extensionElement);
    }
}
